/**
 * @文件:    flexible_button.c
 * @作者:    MurphyZhao
 * @日期:    2018-09-29
 * 
 * 版权所有 (c) 2018-2019 MurphyZhao <d2014zjt@163.com>
 *               https://github.com/murphyzhao
 * 保留所有权利.
 * 许可证标识符: Apache-2.0
 *
 * 根据 Apache 许可证 2.0 版（“许可证”）获得许可；
 * 除非符合许可证，否则您不得使用此文件。
 * 您可以在以下网址获取许可证副本：
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * 除非适用法律要求或书面同意，软件按“原样”分发，
 * 不附带任何明示或暗示的担保或条件。
 * 有关许可证下权限和限制的具体语言，请参见许可证。
 *
 * 变更日志:
 * 日期         作者         说明
 * 2018-09-29   MurphyZhao   首次添加
 * 2019-08-02   MurphyZhao   迁移代码到 murphyzhao 仓库
 * 
*/

#include "flexible_button.h"
#include <string.h>
#include <stdio.h>

static flex_button_t *btn_head = NULL;

#define EVENT_CB_EXECUTOR(button) if(button->cb) button->cb((flex_button_t*)button)
#define MAX_BUTTON_CNT 16

static uint16_t trg = 0;
static uint16_t cont = 0;
static uint16_t keydata = 0xFFFF;
static uint16_t key_rst_data = 0xFFFF;
static uint8_t button_cnt = 0;

/**
 * @brief 注册一个用户按键
 * 
 * @param button: 按键结构体实例
 * @return 已注册的按键数量
*/
int8_t flex_button_register(flex_button_t *button)
{
    flex_button_t *curr = btn_head;
    
    if (!button || (button_cnt > MAX_BUTTON_CNT))
    {
        return -1;
    }

    while (curr)
    {
        if(curr == button)
        {
            return -1;  // 已存在
        }
        curr = curr->next;
    }

    button->next = btn_head;
    button->status = 0;
    button->event = FLEX_BTN_PRESS_NONE;
    button->scan_cnt = 0;
    button->click_cnt = 0;
    btn_head = button;
    key_rst_data = key_rst_data << 1;
    button_cnt ++;
    return button_cnt;
}

/**
 * @brief 一次扫描周期内读取所有按键值
 * 
 * @param void
 * @return 无
*/
static void flex_button_read(void)
{
    flex_button_t* target;
    uint16_t read_data = 0;
    keydata = key_rst_data;
    int8_t i = 0;

    for(target = btn_head, i = 0;
        (target != NULL) && (target->usr_button_read != NULL);
        target = target->next, i ++)
    {
        keydata = keydata |
                  (target->pressed_logic_level == 1 ?
                  ((!(target->usr_button_read)()) << i) :
                  ((target->usr_button_read)() << i));
    }

    read_data = keydata^0xFFFF;
    trg = read_data & (read_data ^ cont);
    cont = read_data;
}

/**
 * @brief 一次扫描周期内处理所有按键事件
 *        必须在调用 'flex_button_read' API 后使用
 * 
 * @param void
 * @return 无
*/
static void flex_button_process(void)
{
    int8_t i = 0;
    flex_button_t* target;

    for (target = btn_head, i = 0; target != NULL; target = target->next, i ++)
    {
        if (target->status > 0)
        {
            target->scan_cnt ++;
        }

        switch (target->status)
        {
            case 0: /* 默认状态 */
                if (trg & (1 << i)) /* 按下 */
                {
                    target->scan_cnt = 0;
                    target->click_cnt = 0;
                    target->status = 1;
                    target->event = FLEX_BTN_PRESS_DOWN;
                    EVENT_CB_EXECUTOR(target);
                }
                else
                {
                    target->event = FLEX_BTN_PRESS_NONE;
                }
                break;

            case 1: /* 按下状态 */
                if (!(cont & (1 << i))) /* 松开 */
                {
                    target->status = 2;
                }
                else if ((target->scan_cnt >= target->short_press_start_tick) &&
                        (target->scan_cnt < target->long_press_start_tick))
                {
                    target->status = 4;
                    target->event = FLEX_BTN_PRESS_SHORT_START;
                    EVENT_CB_EXECUTOR(target);
                }
                break;

            case 2: /* 松开状态 */
                if ((target->scan_cnt < target->click_start_tick))
                {
                    target->click_cnt++; // 1
                    
                    if (target->click_cnt == 1)
                    {
                        target->status = 3;  /* 双击检测 */
                    }
                    else
                    {
                        target->click_cnt = 0;
                        target->status = 0;
                        target->event = FLEX_BTN_PRESS_DOUBLE_CLICK;
                        EVENT_CB_EXECUTOR(target);
                    }
                }
                else if ((target->scan_cnt >= target->click_start_tick) &&
                    (target->scan_cnt < target->short_press_start_tick))
                {
                    target->click_cnt = 0;
                    target->status = 0;
                    target->event = FLEX_BTN_PRESS_CLICK;
                    EVENT_CB_EXECUTOR(target);
                }
                else if ((target->scan_cnt >= target->short_press_start_tick) &&
                    (target->scan_cnt < target->long_press_start_tick))
                {
                    target->click_cnt = 0;
                    target->status = 0;
                    target->event = FLEX_BTN_PRESS_SHORT_UP;
                    EVENT_CB_EXECUTOR(target);
                }
                else if ((target->scan_cnt >= target->long_press_start_tick) &&
                    (target->scan_cnt < target->long_hold_start_tick))
                {
                    target->click_cnt = 0;
                    target->status = 0;
                    target->event = FLEX_BTN_PRESS_LONG_UP;
                    EVENT_CB_EXECUTOR(target);
                }
                else if (target->scan_cnt >= target->long_hold_start_tick)
                {
                    /* 长按保持后松开，不处理 */
                    target->click_cnt = 0;
                    target->status = 0;
                    target->event = FLEX_BTN_PRESS_LONG_HOLD_UP;
                    EVENT_CB_EXECUTOR(target);
                }
                break;

            case 3: /* 双击检测 */
                if (trg & (1 << i))
                {
                    target->click_cnt++;
                    target->status = 2;
                    target->scan_cnt --;
                }
                else if (target->scan_cnt >= target->click_start_tick)
                {
                    target->status = 2;
                }
                break;

            case 4: /* 短按状态 */
                if (!(cont & (1 << i))) /* 松开 */
                {
                    target->status = 2;
                }
                else if ((target->scan_cnt >= target->long_press_start_tick) &&
                        (target->scan_cnt < target->long_hold_start_tick))
                {
                    target->status = 5;
                    target->event = FLEX_BTN_PRESS_LONG_START;
                    EVENT_CB_EXECUTOR(target);
                }
                break;

            case 5: /* 长按状态 */
                if (!(cont & (1 << i))) /* 松开 */
                {
                    target->status = 2;
                }
                else if (target->scan_cnt >= target->long_hold_start_tick)
                {
                    target->status = 6;
                    target->event = FLEX_BTN_PRESS_LONG_HOLD;
                    EVENT_CB_EXECUTOR(target);
                }
                break;

            case 6: /* 长按保持状态 */
                if (!(cont & (1 << i))) /* 松开 */
                {
                    target->status = 2;
                }
                break;
        }
    }
}

/**
 * flex_button_event_read
 * 
 * @brief 获取指定按键的事件
 * 
 * @param button: 按键结构体实例
 * @return 按键事件
*/
flex_button_event_t flex_button_event_read(flex_button_t* button)
{
    return (flex_button_event_t)(button->event);
}

/**
 * flex_button_scan
 * 
 * @brief 启动按键扫描。
 *        需要在指定周期内循环调用。
 *        推荐扫描周期：5 - 20ms
 * 
 * @param void
 * @return 无
*/
void flex_button_scan(void)
{
    flex_button_read();
    flex_button_process();
}
